/* ###################################### */
/* ###     Util Engine, by Kalith    ### */
/* ###################################### */
/*            Profiler source             */
/*                                        */
/*  ## : A class that manages code        */
/*  profiling.                            */
/*                                        */

#include "TimeManager.h"
#include "Profiler.h"
#include "Stdhelper.h"
#include "Log.h"
#include "Math.h"
#include "Timers.h"

#ifdef WIN32
    #include <time.h>
    #include <sys/timeb.h>
#else
    #include <sys/time.h>

#endif

using namespace std;

namespace Util
{
    const Str TimeManager::CLASS_NAME = "TimeManager";

    TimeManager::TimeManager()
    {
        dDelta_ = 0.017;
        uiWorstFPS_ = UInt::INF;
        pGetTimeFunction_ = nullptr;
    }

    TimeManager::~TimeManager()
    {
    }

    void TimeManager::SetGetTimeFunction( GetTimeFunction pFunc )
    {
        pGetTimeFunction_ = pFunc;
    }

    void TimeManager::Initialize()
    {
        pFPSTimer_ = Refptr<Timer>(new Timer(Timer::START_NOW));
        dLastTime_ = dCurTime_ = dStart_ = dLast_ = GetTime();

        dProfileTime_ = 0.0;
        uiFPS_ = 0;
        dDelta_ = 0.017;
        uiAvgFPS_ = 0;
        uiFrameNbr_ = 0;
        uiBestFPS_ = 0;
        uiWorstFPS_ = UInt::INF;
    }

    void TimeManager::Update()
    {
        // Get delta time
        dLastTime_ = dCurTime_;
        dCurTime_ = GetTime();
        dDelta_ = dCurTime_ - dLastTime_;

        // Update FPS
        static UInt uiFrameRateCount = 0;
        static UInt uiFrameCount = 0;
        ++uiFrameCount;

        if (dDelta_ < 0.0001)
            uiFrameRateCount += 10000;
        else
            uiFrameRateCount += UInt(1.0/dDelta_);

        if (pFPSTimer_ && pFPSTimer_->GetElapsed() > 1.0)
        {
            pFPSTimer_->Zero();

            uiFPS_ = uiFrameRateCount/uiFrameCount;
            uiFrameRateCount = 0;
            uiFrameCount = 0;

            uiAvgFPS_ += uiFPS_;
            ++uiFrameNbr_;

            uiBestFPS_ = UInt::Max(uiBestFPS_, uiFPS_);
            uiWorstFPS_ = UInt::Min(uiWorstFPS_, uiFPS_);
        }
    }

    Double TimeManager::GetDelta() const
    {
        if (dDelta_ > 0.0001)
            return dDelta_;
        else
        {
            if (uiFPS_ != 0)
                return 1.0/uiFPS_.Get();
            else
                return 0.0;
        }
    }

    const UInt& TimeManager::GetFPS() const
    {
        return uiFPS_;
    }

    UInt TimeManager::GetAverageFPS() const
    {
        if (uiFrameNbr_ == 0)
            return UInt(UInt::INTEGER_NAN);
        else
            return uiAvgFPS_/uiFrameNbr_;
    }

    const UInt& TimeManager::GetBestFPS() const
    {
        return uiBestFPS_;
    }

    const UInt& TimeManager::GetWorstFPS() const
    {
        return uiWorstFPS_;
    }

    Double TimeManager::GetTime() const
    {
        if (pGetTimeFunction_)
            return (*pGetTimeFunction_)();
        else
            return 0.0;
    }

    Str TimeManager::GetPlayTime() const
    {
        Double dTime = GetTime();
        UInt uiHours(Double::RoundDown(dTime/3600.0));
        dTime -= Double(uiHours)*3600.0;
        UInt uiMinutes(Double::RoundDown(dTime/60.0));
        dTime -= Double(uiMinutes)*60.0;
        UInt uiSeconds(Double::RoundDown(dTime));
        dTime -= Double(uiSeconds);
        UInt uiMilliseconds(Double::RoundDown(dTime*1000));

        Str sTime = "[";
        sTime += Str::Convert(uiHours, 2) + ":";
        sTime += Str::Convert(uiMinutes, 2) + ":";
        sTime += Str::Convert(uiSeconds, 2) + ":";
        sTime += Str::Convert(uiMilliseconds, 3) + "]";

        return sTime;
    }

    Ptr<Profiler> TimeManager::GetProfiler( const UInt& uiGroup, const Str& sName, const Bool& bRecord )
    {
        if (bProfiling_ == true)
        {
            bProfiled_ = true;

            if (!lProfilerList_.Find(sName))
            {
                lProfilerList_[sName] = Profiler(uiGroup, sName, bRecord);
            }

            return &lProfilerList_[sName];
        }
        else
            return nullptr;
    }

    void TimeManager::SetProfiling( const Bool& bProfiling )
    {
        // Update total profiling time
        if (bProfiling && !bProfiling_)
            dLast_ = GetTime();
        else if (!bProfiling && bProfiling_)
            dProfileTime_ = GetTime() - dLast_;

        bProfiling_ = bProfiling;
    }

    void TimeManager::Print( const UInt& uiGroup )
    {
        if (bProfiled_)
        {
            if (bProfiling_)
            {
                dProfileTime_ = GetTime() - dLast_;
                bProfiling_ = false;
            }

            Log("\n");
            if (!uiGroup.IsValid())
                Log("# Profiling info : "+ Str::Convert(dProfileTime_, 0, 2) +" s of profiling");
            else
                Log("# Profiling info for group ["+ uiGroup +"] : "+ dProfileTime_ +" s of profiling");
            map<Str, Profiler>::iterator iter;
            foreach (iter, lProfilerList_)
            {
                Ptr<Profiler> pProfiler = &iter->second;
                if ( (pProfiler->GetGroup() == uiGroup) || (!uiGroup.IsValid()) )
                    pProfiler->PrintProfile(dProfileTime_);
            }
            Log("# end.");
        }
    }

    UInt TimeManager::GetYear() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_year + 1900;
    }

    UInt TimeManager::GetMonth() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_mon + 1;
    }

    UInt TimeManager::GetDayName() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_wday + 1;
    }

    UInt TimeManager::GetDay() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_mday;
    }

    UInt TimeManager::GetHour() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_hour;
    }

    UInt TimeManager::GetMinutes() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_min;
    }

    UInt TimeManager::GetSeconds() const
    {
        time_t iTimestamp = time(nullptr);
        return localtime(&iTimestamp)->tm_sec;
    }

    UInt TimeManager::GetMicroseconds() const
    {
    #ifdef WIN32
        timeb tb;
        ftime(&tb);
        return (uint)tb.millitm*1000;
    #else
        timeval tv;
        gettimeofday(&tv, nullptr);
        return tv.tv_usec;
    #endif
    }
}
